package dpss.node.udp;

import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;

import dpss.commons.CustomLogger;
import dpss.commons.ServerNode;
import dpss.commons.UDPMessage;
import dpss.node.server.Server;

/**
 * The thread that listens to incoming UDP requests
 * @author musab.mirza
 *
 */
public class UDPListener extends Thread{
	
	private static CustomLogger logger = CustomLogger.getInstance();
	private DatagramSocket socket;
	private Server server;
	private InetSocketAddress replicaManagerSocketAddress;
	

	/**
	 * Construct with references
	 * 
	 * @param socket
	 * @param server
	 * @param replicaManagerSocketAddress
	 */
	public UDPListener(DatagramSocket socket, Server server,
			InetSocketAddress replicaManagerSocketAddress) {
		this.socket = socket;
		this.server = server;
		this.replicaManagerSocketAddress = replicaManagerSocketAddress;
	}
	
	
	/**
	 * Listener
	 */
	public void run(){
		for (;;) {

			UDPMessage messageReceived = UDPMessage.receiveUDPMessage(socket);
			
			logger.log("Received: " + messageReceived.getMessage());
			
			// IF DPSS RELATED REQUEST
			if (messageReceived.getMessage().startsWith("dpss_")){
				
				server.tackleDpssRequest(messageReceived);
				
			} else {

				// ALL NON DPSS REQUESTS
				switch (messageReceived.getMessage()) {
	
				// Initialize
				case "initialize":
					requestInitializeServer(messageReceived);
					break;
	
				// Uninitialize the server
				case "uninitialize":
					requestUninitializeServer();
					break;
					
				// Disconnect
				case "disconnect":
					requestDisconnect();
					break;
				
				// All leaders info
				case "allLeadersInfo":
					requestAllLeadersInfo(messageReceived);
					break;
				
				// Update replas info
				case "updateReplicas":
					requestUpdateReplicas(messageReceived);
					break;
					
				// Set the faulty flag
				case "setFaulty":
					requestSetFaulty(messageReceived);
					break;
				
				// Unknown
				default:
					logger.log("Unknown message received, ignoring it.");
				}
			}
		}
	}
	


	/**
	 * Sets the server to faulty or unfaulty
	 * @param messageReceived
	 */
	private void requestSetFaulty(UDPMessage messageReceived) {
		boolean faultyFlagToSet =(boolean) messageReceived.getData();
		server.getServerNode().setFaulty(faultyFlagToSet);
		if (faultyFlagToSet == true)
			logger.log("Made this server FAULTY");
		else
			logger.log("Make this server UNFAULTY");
	}


	/**
	 * Updates the replicas information
	 * @param messageReceived
	 */
	private void requestUpdateReplicas(UDPMessage messageReceived) {
		server.setMyReplicas( (HashSet<InetSocketAddress>) messageReceived.getData());
		logger.log("Updated server replicas information: " + server.getMyReplicas().toString());
	}


	/**
	 * Updates info about all other leaders
	 * @param messageReceived
	 */
	private void requestAllLeadersInfo(UDPMessage messageReceived) {
		HashMap<String, InetSocketAddress> allLeadersInfo = (HashMap<String, InetSocketAddress>) messageReceived.getData();
		server.updateOtherLeadersInfo(allLeadersInfo);
	}


	/**
	 * Uninitializes the server so it can be programmed again
	 */
	private void requestUninitializeServer() {
		
		logger.log("Server uninitialization request received");
		server.uninitialize();
		logger.log("Server has been uninitialized, waiting to be initialized again");
		
		// Send back a UDP response saying everything was fine
		UDPMessage messageToSend = new UDPMessage("uninitializationResponse", null, true, "");
		UDPMessage.sendMessage(socket, messageToSend, replicaManagerSocketAddress);
	}

	/**
	 * Initializes a new server with the information obtained
	 * 
	 * @param messageReceived
	 */
	private void requestInitializeServer(UDPMessage messageReceived) {
		
		logger.log("Server initialization request received");

		HashMap<String, Object> data = (HashMap<String, Object>) messageReceived
				.getData();

		HashMap<String, InetSocketAddress> otherLeaders = (HashMap<String, InetSocketAddress>) data
				.get("otherLeaders");
		ServerNode serverNode = (ServerNode) data.get("serverNode");

		server.initialize(serverNode, otherLeaders, (InetSocketAddress) data.get("myLeader"));
	
		
		// Send back a UDP response saying everything was fine
		UDPMessage messageToSend = new UDPMessage("initializationResponse", null, true, "");
		UDPMessage.sendMessage(socket, messageToSend, replicaManagerSocketAddress);
	}

	/**
	 * Disconnects completely from the replica Manager and exits
	 * 
	 * @param messageReceived
	 */
	private void requestDisconnect() {
		logger.log("Replica Manager disconnected this node. Exiting, have a good day!");
		System.exit(0);
	}

}
